﻿// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

using System;

using NUnit.Framework;

using SRTSolutions.Elevate;
using SRTSolutions.Elevate.PatternMatching;

namespace SRTSolutions.Elevate.Guide
{

#if NET_3_5
#else
    using Tuple=System.Tuple;
#endif

    public class PatternMatching
    {
        /// <summary>
        /// Pattern matching is used in functional languages. It can be thought of as a "switch statement on steriods".
        /// Here's an example of very basic pattern matching using Elevate.
        /// </summary>
        [Test]
        public void PatternMatchingWithFunctions()
        {
            //given a value
            var value = "alpha";

            var result = value.Match() //we can start a pattern match like this
                .With(string.IsNullOrEmpty, stringValue => "empty") //causes the pattern match to return "empty" if value is null or empty
                .With(stringValue => value.Contains("a"), stringValue => "contains a!") //match any string containing "a"
                .EndMatch();

            Assert.AreEqual("contains a!", result);
        }

        [Test]
        public void PatternMatchingWithMatchPatternsAsValues()
        {
            //given a value
            var value = "gamma";

            //we can equality match against values without having to specify
            //a condition function
            var result = value.Match()
                .With("alpha", stringValue => "1st letter = " + stringValue)
                .With("beta", stringValue => "2nd letter = " + stringValue)
                .With("gamma", stringValue => "3rd letter = " + stringValue)
                .EndMatch();

            Assert.AreEqual("3rd letter = gamma", result);
        }

        [Test]
        public void PatternMatchingWithMatchPatternsAndResultsAsValues()
        {
            //given a value
            var value = "gamma";

            //we can also match without having to speficy a lambda for either the
            //match predicate or the result
            var result = value.Match()
                .With("alpha", "first letter")
                .With("beta", "second letter")
                .With("gamma", "third letter")
                .EndMatch();

            Assert.AreEqual("third letter", result);
        }

        [Test]
        public void IncompletePatternMatching()
        {
            //given a value
            var stringValue = "alpha, beta";

            //if we construct a pattern match that does not match the value
            TestDelegate incompleteMatch = () =>
                stringValue.Match()
                .With(value => value == "alpha", value => "Found foo")
                .With(value => value == "beta", value => "Found bar")
                .EndMatch();

            //evaluating it will throw
            Assert.Throws<IncompleteMatchException>(incompleteMatch);
        }

        [Test]
        public void PatternMatchingWithElse()
        {
            //given a value
            var stringValue = "alpha, beta";

            //we can add Else to cause the incomplete match from above to have a
            //default case
            var elseMatch =
                stringValue.Match()
                .With(value => value == "alpha", value => "Found foo")
                .With(value => value == "beta", value => "Found bar")
                .Else(value => "default!")
                .EndMatch();

            Assert.AreEqual("default!", elseMatch);
        }

        [Test]
        public void PatternMatchingElseWithValues()
        {
            //given a value
            var stringValue = "alpha, beta";

            //we can specify else as a concrete value without having
            //to use the input value
            var elseMatch =
                stringValue.Match()
                .With(value => value == "alpha", value => "Found foo")
                .With(value => value == "beta", value => "Found bar")
                .Else("default!")
                .EndMatch();

            Assert.AreEqual("default!", elseMatch);
        }

        [Test]
        public void DeconstructingTuplesWithPatternMatching()
        {
            //given a tuple
            var tuple = Tuple.Create("Da Bears", 2);

            //pattern matching can automatically break down the tuple for us
            var result = tuple.Match()
                .With((teamName, wins) => teamName == "Da Bears", (teamName, wins) => wins)
                //the values from the input tuple are automatically split up for us
                //this can be very helpful for giving descriptive names to the different
                //parts of the tuple.
                .EndMatch();

            Assert.AreEqual(result, 2);
        }

        [Test]
        public void EasierTuplePatternMatching()
        {
            //given a tuple
            var tuple = Tuple.Create("Da Bears", 2);

            //We can avoid having to specify the arguments explicitly for the
            //match portion of the predicate like this
            var result = tuple.Match()
                .WithSecond(1, (teamName, wins) => wins + 1)
                .WithFirst("Da Bears", (teamName, wins) => wins)
                .EndMatch();

            Assert.AreEqual(result, 2);
        }

        #region Unit Tests

        [Test]
        public void OneMatchMisses()
        {
            var stringValue = "alpha, beta";

            TestDelegate incompleteMatch = () =>
                stringValue.Match()
                .With(value => value == "beta", value => "Found bar")
                .EndMatch();

            Assert.Throws<IncompleteMatchException>(incompleteMatch);
        }

        [Test]
        public void PatternMatchingMultipleMatchesDeconstruction()
        {
            var tuple = Tuple.Create("Da Bears", 2);

            var result = tuple.Match()
                .WithFirst("bad match", (teamName, wins) => wins + 1)
                .WithFirst("Da Bears", (teamName, wins) => wins)
                .EndMatch();

            Assert.AreEqual(result, 2);
        }

        [Test]
        public void PatternMatchingMultipleMatchesSecond()
        {
            var tuple = Tuple.Create("Da Bears", 2);

            var result = tuple.Match()
                .WithSecond(0, (teamName, wins) => teamName + " is winless")
                .WithSecond(2, (teamName, wins) => teamName + " won two games")
                .EndMatch();

            Assert.AreEqual(result, "Da Bears won two games");
        }

        [Test]
        public void PatternMatchingMultipleMatchesMixed()
        {
            var tuple = Tuple.Create("Da Bears", 2);

            var result = tuple.Match()
                .WithFirst("bad match", (teamName, wins) => "fail")
                .WithSecond(0, (teamName, wins) => teamName + " is winless")
                .WithSecond(2, (teamName, wins) => teamName + " won two games")
                .EndMatch();

            Assert.AreEqual(result, "Da Bears won two games");
        }

        [Test]
        public void ElseDeconstruction()
        {
            var tuple = Tuple.Create(1, 2);

            var result = tuple.Match()
                .With((x, y) => x == 5, (x, y) => y)
                .Else((x, y) => x)
                .EndMatch();

            Assert.AreEqual(result, 1);
        }

        [Test]
        public void PatternMatchingTuplesWithMultipleDeconstruction()
        {
            var tuple = Tuple.Create(1, 2);

            var result = tuple.Match()
                .With((x, y) => x == 5, (x, y) => y)
                .With((x, y) => x == 1, (x, y) => y)
                .EndMatch();

            Assert.AreEqual(result, 2);
        }

        [Test]
        public void PatternMatchingWithNullPredicateFunction()
        {
            Func<string, bool> tmp = null;
            Assert.Throws<ArgumentNullException>(() => "a".Match().With(tmp, val => val));
        }

        [Test]
        public void PatternMatchingWithNullResultFunction()
        {
            Func<string, string> tmp = null;
            Assert.Throws<ArgumentNullException>(() => "a".Match().With(val => true, tmp));
        }

        [Test]
        public void PatternMatchingWithNullResultFunctionValueMatch()
        {
            Func<string, string> tmp = null;
            Assert.Throws<ArgumentNullException>(() => "a".Match().With("a", tmp));
        }

        [Test]
        public void PatternMatchingWithNullPredicateFunctionOffContext()
        {
            Func<string, bool> tmp = null;

            var context = "a".Match().With(val => true, val2 => val2);

            Assert.Throws<ArgumentNullException>(() => context.With(tmp, val3 => val3));
        }

        [Test]
        public void PatternMatchingWithNullResultFunctionOffContext()
        {
            Func<string, string> tmp = null;

            var context = "a".Match().With(val => true, val2 => val2);

            Assert.Throws<ArgumentNullException>(() => context.With(val3 => true, tmp));
        }

        [Test]
        public void PatternMatchingWithNullResultFunctionValueMatchOffContext()
        {
            Func<string, string> tmp = null;

            var context = "a".Match().With(val => true, val2 => val2);

            Assert.Throws<ArgumentNullException>(() => context.With("a", tmp));
        }

        #endregion

    }
}
